#include "FAQCipher.h"
#include "mode/ECB.h"
#include "mode/CBC.h"
#include "mode/CFB.h"
#include "mode/OFB.h"
#include "mode/CTR.h"
#include "mode/CTS.h"
#include "mode/GCM.h"
#include "mode/PCBC.h"
#include "lang/System.h"
#include "padding/NoPadding.h"
#include "padding/PKCS7Padding.h"
#include "crypto/FAQCryptoRegister.h"
#include "algorithms/ialgorithm/FAQBlockAlgorithm.h"
#include "algorithms/ialgorithm/FAQStreamAlgorithm.h"
#include "algorithms/ialgorithm/FAQKeyAlgorithm.h"
using namespace yzrilyzr_array;
using namespace yzrilyzr_lang;
namespace yzrilyzr_crypto{
	FAQCipher::FAQCipher(){
		mode=std::make_shared<ECB>();
		padding=std::make_shared<PKCS7Padding>();
	}
	void FAQCipher::onUpdate(){
		if(std::dynamic_pointer_cast<FAQBlockAlgorithm>(algorithm)){
			ByteArray block(getBlockSize());
			if(encMode == ENCRYPT_MODE){
				while(inBuffer.available() >= block.length){
					inBuffer.read(block);
					ByteArray encblock=mode->encrypt(block, *algorithm);
					outBuffer.write(encblock, 0, encblock.length);
				}
				inBuffer.compact();
			} else if(encMode == DECRYPT_MODE){
				while(inBuffer.available() > block.length){
					inBuffer.read(block);
					ByteArray encblock=mode->decrypt(block, *algorithm);
					outBuffer.write(encblock, 0, encblock.length);
				}
				inBuffer.compact();
			}
		} else if(std::dynamic_pointer_cast<FAQStreamAlgorithm>(algorithm)){
			if(encMode == ENCRYPT_MODE){
				ByteArray encblock=algorithm->encrypt(inBuffer.toArray());
				inBuffer.compact();
				outBuffer.write(encblock, 0, encblock.length);
			} else if(encMode == DECRYPT_MODE){
				ByteArray encblock=algorithm->decrypt(inBuffer.toArray());
				inBuffer.compact();
				outBuffer.write(encblock, 0, encblock.length);
			}
		}
	}
	void FAQCipher::doFinal(){
		if(std::dynamic_pointer_cast<FAQBlockAlgorithm>(algorithm)){
			if(encMode == ENCRYPT_MODE){
				//last block
				int blockSize=getBlockSize();
				if(inBuffer.available() == 0){
					if((
						std::dynamic_pointer_cast<ECB>(mode)
						|| std::dynamic_pointer_cast<CBC>(mode)
						|| std::dynamic_pointer_cast<CFB>(mode)
						|| std::dynamic_pointer_cast<OFB>(mode)
						|| std::dynamic_pointer_cast<CTR>(mode)
						) && std::dynamic_pointer_cast<PKCS7Padding>(padding) ||
						std::dynamic_pointer_cast<GCM>(mode)
						){
					} else return;
				}
				ByteArray lastBlock=inBuffer.toArray();//lastBlock.length<=blockSize且不等于0
				if(std::dynamic_pointer_cast<CTS>(mode) && std::dynamic_pointer_cast<NoPadding>(mode)){
					ByteArray fb=outBuffer.toArray();
					outBuffer.reset();
					outBuffer.write(fb, 0, fb.length - blockSize);
				}
				//
				ByteArray p=padding->padding(lastBlock, 0, lastBlock.length, blockSize);
				//enc
				ByteArray encblock=mode->encryptFinal(p, *algorithm);
				outBuffer.write(encblock, 0, encblock.length);
				return;
			} else if(encMode == DECRYPT_MODE){
				ByteArray lastBlock=inBuffer.toArray();//lastBlock.length<=blockSize且不等于0
				if(lastBlock.length == 0){
					outBuffer.write(lastBlock);
					return;
				}
				//dec
				ByteArray dec=mode->decryptFinal(lastBlock, *algorithm);
				//last block unpadding
				int pos=padding->unpadding(dec);
				ByteArray unpaddedBlock(pos);
				System::arraycopy(dec, 0, unpaddedBlock, 0, unpaddedBlock.length);
				outBuffer.write(unpaddedBlock, 0, unpaddedBlock.length);
				return;
			}
		} else if(std::dynamic_pointer_cast<FAQStreamAlgorithm>(algorithm)){
			return;
		}
		throw RuntimeException("?");
	}
	void FAQCipher::reset(){}
	u_index FAQCipher::getBlockSize()const{
		if(auto balg=std::dynamic_pointer_cast<FAQBlockAlgorithm>(algorithm))
			return balg->getBlockSize();
		return 0;
	}
	void FAQCipher::setTagLength(u_index tagLength){
		this->tagLength=tagLength;
	}
	void FAQCipher::setIV(const ByteArray & iv){
		setIV(iv, 0, iv.length);
	}
	void FAQCipher::setIV(const ByteArray & iv1, u_index off, u_index len){
		this->iv=ByteArray(len);
		System::arraycopy(iv1, off, this->iv, 0, len);
	}
	void FAQCipher::init(const yzrilyzr_lang::String & transformArg, int encmode, const FAQKey & key){
		inBuffer.reset();
		outBuffer.reset();
		yzrilyzr_lang::String transform=transformArg;
		if(transform == nullptr){
			transform="Null/CBC/PKCS7Padding";
			iv=ByteArray(16);
		}
		auto ts=transform.split("/");
		encMode=encmode;
		if(ts.length == 1){
			algorithm=FAQCryptoRegister::getCipher(ts[0]);
			std::dynamic_pointer_cast<FAQKeyAlgorithm>(algorithm)->setKey(key);
		} else if(ts.length == 3){
			algorithm=FAQCryptoRegister::getCipher(ts[0]);
			std::dynamic_pointer_cast<FAQKeyAlgorithm>(algorithm)->setKey(key);
			if(iv != nullptr){
				if(auto balg=std::dynamic_pointer_cast<FAQBlockAlgorithm>(algorithm)){
					if(iv.length > balg->getBlockSize()){
						setIV(iv, 0, balg->getBlockSize());
					}
				}
			}
			const String md=ts[1].toUpperCase();
			if(md == "ECB")mode=std::make_shared<ECB>();
			else if(md == "OFB")mode=std::make_shared<OFB>(iv);
			else if(md == "CBC")mode=std::make_shared<CBC>(iv, 0, iv.length);
			else if(md == "CFB")mode=std::make_shared<CFB>(iv);
			else if(md == "CTR")mode=std::make_shared<CTR>(iv);
			else if(md == "CTS")mode=std::make_shared<CTS>(iv);
			else if(md == "PCBC")mode=std::make_shared<PCBC>(iv);
			else if(md == "GCM"){
				auto gcm=std::make_shared<GCM>(iv, ByteArray(), tagLength);
				mode=gcm;
				gcm->init(*algorithm);
			} else throw IndexOutOfBoundsException("Unknown Mode");

			const String pad=ts[2].toUpperCase();
			if(pad == "NOPADDING")padding=std::make_shared <NoPadding>();
			else if(pad == "PKCS7PADDING")padding=std::make_shared <PKCS7Padding>();


		} else throw IndexOutOfBoundsException("Unknown generate:" + transform);
	}
	u_index FAQCipher::getOutputSize(u_index inLen)const{
		return 0;//Math.max(outputBuffer.size(),inputBuffer.size())+getBlockSize()+inLen;
	}
	void FAQCipher::updateAAD(const ByteArray & input){
		updateAAD(input, 0, input.length);
	}
	void FAQCipher::updateAAD(const ByteArray & input, u_index offset, u_index length){
		std::dynamic_pointer_cast<GCM>(mode)->getGHash().updateAAD(input, offset, length);
	}
}